#include <QUuid>
#include "_isstructs.h"
#include "insim.h"

namespace LFS
{
    //! Default constructor.
    InSim::InSim()
    {
        //! Connect signals and slots.
#define CONNECTOR(a) connect(&insimSocket, SIGNAL(a), this, SLOT(a))
        CONNECTOR(connected());
        CONNECTOR(disconnected());
        CONNECTOR(hostFound());
        CONNECTOR(readyRead());
        CONNECTOR(error(QAbstractSocket::SocketError));
    }
    
    //! Default destructor.
    InSim::~InSim()
    {
        //! Send a quit message to LFS.
        sendTinyPacket(TINY_CLOSE);
        //! Try blindly to disconnect.
        insimSocket.disconnectFromHost();
    }
    
    //! Setup data for connection.
    void InSim::setConnectionData(QString &newClientName, QString &newHostname,
                                  quint16 newPort)
    {
        insimClientName = newClientName;
        insimHostname = newHostname;
        insimPort = newPort;
    }
    
    //! Query connection data.
    void InSim::getConnectionData(QString &clientName, QString &hostname, quint16 &port)
    {
        clientName = insimClientName;
        hostname = insimHostname;
        port = insimPort;
    }
    
    //! Connect to the specified host.
    void InSim::connectToHost(QString &newClientName, QString &newHostname, quint16 newPort)
    {
        setConnectionData(newClientName, newHostname, newPort);
        connectToHost();
    }
    
    //! Connect to the stored host.
    void InSim::connectToHost()
    {
        if(insimHostname.isEmpty())
        {
            emit insimErrorString(tr("Error: connection data not set."));
        }
        else
        {
            insimSocket.connectToHost(insimHostname, insimPort);
        }
    }
    
    //! Disconnect the current connection.
    void InSim::disconnectFromHost()
    {
        //! Send close -message to LFS.
        sendTinyPacket(TINY_CLOSE);
        insimSocket.disconnectFromHost();
    }
    
    //! Return a pointer to the implementation.
    InSim *InSim::getInSim()
    {
        return this;
    }
    
    //! Is connection up?
    bool InSim::isConnected()
    {
        QAbstractSocket::SocketState state = insimSocket.state();
#ifndef QT_NO_DEBUG_OUTPUT
        debugSocketState(state);
#endif
        return state==QAbstractSocket::ConnectedState;
    }
    
    //! Send a tiny packet.
    qint64 InSim::sendTinyPacket(quint8 subType, quint8 requestInfo)
    {
        //! This call hoses messageData.
        InSimTiny *tiny = getMessageStructure<InSimTiny> ();
        //! Set values.
        tiny->Type = ISP_TINY;
        tiny->SubT = subType;
        tiny->ReqI = requestInfo;
        /**
         * Since tiny is contained within messageData, we can
         * use it directly to send the message. No need for
         * casting tiny into 'const char *'.
         */
        return insimSocket.write(messageData, tiny->Size);
    }
    
    //! A template function to return a pointer to any of InSim's structures.
    template<class T> T *InSim::getMessageStructure()
    {
        //! Clear contents.
        messageData.fill('\0', sizeof(T));
        //! Initialize struct's size. It's always the first byte of the structure.
        messageData[0] = (char) sizeof(T);
        //! Done, return.
        return (T*) messageData.constData();
    }
    
    //! Called when TCP socket has set up the connection.
    void InSim::connected()
    {
        //! Automatically initialize LFS connection.
        InSimInitialize *isi = getMessageStructure<InSimInitialize> ();
        isi->Type = ISP_ISI;
        isi->UDPPort = 0;
        isi->Flags = 0;
        isi->Interval = 0;
        isi->Prefix = '!';
        /**
         * InSim.txt stipulates that the product name must end with \0,
         * and it's length is 16. Take left 15 chars of the client name,
         * leaving the 16th byte for \0.
         */
        QByteArray clientName(insimClientName.toAscii());
        clientName.resize(sizeof(isi->IName)-1);
        clientName.append('\0');
        qstrcpy(isi->IName, clientName);
        if (-1 != insimSocket.write(messageData, isi->Size))
        {
            //! Query the version packet.
            if (-1 != sendTinyPacket(TINY_VER, Request))
            {
                emit insimConnected();
                return;
            }
        }
        emit insimErrorString(tr("Error: InSim initialization failed."));
    }
    
    //! Called when TCP socket has lost the connection.
    void InSim::disconnected()
    {
        //! Propagate this signal.
        emit insimDisconnected();
    }
    
    //! Called on errors from TCP socket.
    void InSim::error(QAbstractSocket::SocketError /*errorThatOccurred*/)
    {
        QString errorString = insimSocket.errorString();
        emit insimErrorString(errorString);
    }
    
    //! Host was found to connect to.
    void InSim::hostFound()
    {
        qDebug("InSim: Host was found.");
    }
    
    //! Data is available for reading.
    void InSim::readyRead()
    {
#define MSG_TYPE_PRINT(a) case a:{qDebug(#a);}break
        qDebug("InSim: Data available for reading.");
        readMessage = insimSocket.readAll();
        InSimTiny *tiny = (InSimTiny *)readMessage.constData();
        switch(tiny->Type)
        {
            MSG_TYPE_PRINT(ISP_NONE);
            MSG_TYPE_PRINT(ISP_ISI);
            MSG_TYPE_PRINT(ISP_VER);
            MSG_TYPE_PRINT(ISP_STA);
            MSG_TYPE_PRINT(ISP_SCH);
            MSG_TYPE_PRINT(ISP_SFP);
            MSG_TYPE_PRINT(ISP_SCC);
            MSG_TYPE_PRINT(ISP_CPP);
            MSG_TYPE_PRINT(ISP_ISM);
            MSG_TYPE_PRINT(ISP_MSO);
            MSG_TYPE_PRINT(ISP_III);
            MSG_TYPE_PRINT(ISP_MST);
            MSG_TYPE_PRINT(ISP_MTC);
            MSG_TYPE_PRINT(ISP_MOD);
            MSG_TYPE_PRINT(ISP_VTN);
            MSG_TYPE_PRINT(ISP_RST);
            MSG_TYPE_PRINT(ISP_NCN);
            MSG_TYPE_PRINT(ISP_CNL);
            MSG_TYPE_PRINT(ISP_CPR);
            MSG_TYPE_PRINT(ISP_NPL);
            MSG_TYPE_PRINT(ISP_PLP);
            MSG_TYPE_PRINT(ISP_PLL);
            MSG_TYPE_PRINT(ISP_LAP);
            MSG_TYPE_PRINT(ISP_SPX);
            MSG_TYPE_PRINT(ISP_PIT);
            MSG_TYPE_PRINT(ISP_PSF);
            MSG_TYPE_PRINT(ISP_PLA);
            MSG_TYPE_PRINT(ISP_CCH);
            MSG_TYPE_PRINT(ISP_PEN);
            MSG_TYPE_PRINT(ISP_TOC);
            MSG_TYPE_PRINT(ISP_FLG);
            MSG_TYPE_PRINT(ISP_PFL);
            MSG_TYPE_PRINT(ISP_FIN);
            MSG_TYPE_PRINT(ISP_RES);
            MSG_TYPE_PRINT(ISP_REO);
            MSG_TYPE_PRINT(ISP_NLP);
            MSG_TYPE_PRINT(ISP_MCI);
            MSG_TYPE_PRINT(ISP_MSX);
            MSG_TYPE_PRINT(ISP_MSL);
            MSG_TYPE_PRINT(ISP_CRS);
            MSG_TYPE_PRINT(ISP_BFN);
            MSG_TYPE_PRINT(ISP_AXI);
            MSG_TYPE_PRINT(ISP_AXO);
            MSG_TYPE_PRINT(ISP_BTN);
            MSG_TYPE_PRINT(ISP_BTC);
            MSG_TYPE_PRINT(ISP_BTT);
        case ISP_TINY:
            switch(tiny->SubT)
            {
                //! Keep connection alive by responding with the same TINY_NONE.
            case TINY_NONE:
                sendTinyPacket(tiny->SubT, tiny->ReqI);
                break;
                MSG_TYPE_PRINT(TINY_VER);
                MSG_TYPE_PRINT(TINY_CLOSE);
                MSG_TYPE_PRINT(TINY_PING);
                MSG_TYPE_PRINT(TINY_REPLY);
                MSG_TYPE_PRINT(TINY_VTC);
                MSG_TYPE_PRINT(TINY_SCP);
                MSG_TYPE_PRINT(TINY_SST);
                MSG_TYPE_PRINT(TINY_GTH);
                MSG_TYPE_PRINT(TINY_MPE);
                MSG_TYPE_PRINT(TINY_ISM);
                MSG_TYPE_PRINT(TINY_REN);
                MSG_TYPE_PRINT(TINY_CLR);
                MSG_TYPE_PRINT(TINY_NCN);
                MSG_TYPE_PRINT(TINY_NPL);
                MSG_TYPE_PRINT(TINY_RES);
                MSG_TYPE_PRINT(TINY_NLP);
                MSG_TYPE_PRINT(TINY_MCI);
                MSG_TYPE_PRINT(TINY_REO);
                MSG_TYPE_PRINT(TINY_RST);
                MSG_TYPE_PRINT(TINY_AXI);
                MSG_TYPE_PRINT(TINY_AXC);
            }
            break;
        case ISP_SMALL:
            switch(tiny->SubT)
            {
                MSG_TYPE_PRINT(SMALL_NONE);
                MSG_TYPE_PRINT(SMALL_SSP);
                MSG_TYPE_PRINT(SMALL_SSG);
                MSG_TYPE_PRINT(SMALL_VTA);
                MSG_TYPE_PRINT(SMALL_TMS);
                MSG_TYPE_PRINT(SMALL_STP);
                MSG_TYPE_PRINT(SMALL_RTP);
                MSG_TYPE_PRINT(SMALL_NLI);
            }
            break;
        };
    }
}

//! Export plugin.
Q_EXPORT_PLUGIN2(insim, LFS::InSim);

//! End of file.
